<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script>
    /*
    发布订阅模式（形式不局限于函数，形式可以是对象等）
    1.一定要有一个中间的全局的容器，用来存储可以被触发的东西（东西指函数，对象）
    2.还需要一个方法，可以往容器中传入东西（函数，对象）
    3.需要一个方法，可以将容器中的东西取出来使用（函数调用，对象的方法调用）

    vue模型是如何实现的？？？？
    更新了vnode就相当于更新了页面，如何触发dom的更新????全局的中间的东西
    存储watcher，vnode以组件为单位，存储了很多的组件，但是只有一个全局的主组件，
    每个组件都会绑定一些data，props等，页面刷新this.updata(->this.render())
    vue中每套组件会对应一套watcher，watcher起到的作用是更新方法，watcher触发
    会更新data，data更新会刷新页面。

    data是响应式的data，读取的时候调用depend方法，将对应的watcher存入全局的watcher中。
    全局的watcher是数组结构，什么时候会读取data？？？模板渲染，vnode生产的时候。
    前边的combine函数虚拟dom生成的时候，对{{}}进行替换的时候会读取data，读取data会
    触发get方法，会把这个数据对应的watcher存入到全局的watcher中。

    设置的时候会调用notify方法，将全局的所有watcher一一触发。
    什么时候设置？？？数据变更的时候。set。数据变更，触发全局的watcher，
    触发后，页面中的数据发生变化，页面中的vnode发生变化，也就表明页面
    发生了变化。

    内存中可以有多个watcher，但是至少有一个watcher。
    vue构造函数中的watcher就是一个watcher，每个组件也都是转换成了watcher。
    如果vue中既不用组件也不用watcher，那么内存中就一个watcher。
    watcher才是最终改变数据的。

    为什么这么处理？？？？
    页面更新以组件为单位，如果页面中只有一个组件（vue实例）不会有性能的损失。
    但是如果页面中有多个组件（多watcher）第一次会有多个组件的watcher存入到
    全局watcher中。如果修改了局部的数据（其中一个组件的数据）表示只会对改
    组件进行diff算法，只会重新生成改组件的抽象语法树，只会访问该组件的watcher，
    也就表示再次往全局watcher存储的只有盖组件的watcher。   
    页面更新的时候也就只需要更新一部分,提高了页面渲染效率。
    
    更新完毕之后其他的watcher从全局watcher移除


    读取时将wacher存入全局容器时，别成为依赖收集
    修改时，将全局watcher取出执行，被称为 派发更新

    有一个全局的event对象，提供on,off,emit方法
    闭包是为了存储eventObjs

    js中基本类型是比较值，引用类型是比较地址，引用类型和基本类型比较是将
    引用类型转换为基本类型，在进行比较
    [] == [] false,js是一种解释型的语言，从左往右解释，解释第一个[]的时候，
    是一个数组，会在内存中创建一个数组，解释到第二个[]的时候会创建第二个数组，
    两个数组在内存中属于不同的数组，为false
    {} == {} false
    */
   var event = (function() {

    eventObjs = {};

    return {
      /*注册事件，可以连续注册，可以注册多个事件*/
      on: function(type, handle) {
        // eventObjs[type]如果有值就直接push，没有值给eventObjs[type]赋值为[]
        (eventObjs[type] || (eventObjs[type] = [])).push(handle)
      },
      /*
      移除事件，没有参数，移除所有事件，
      只带有事件名的参数，移除这个事件名下的所有事件，
      两个参数，移除某一事件的具体处理函数
      */
      off: function(type, handle) {
        if(arguments.length === 0) {
          // 没有参数，移除所有事件
          eventObjs = {};
        } else if(arguments.length === 1) {
          // 只有事件类型，移除该事件的所有类型
          eventObjs[type] = [];
        } else if(arguments.length === 2) {
          // 移除type事件的handle处理函数, 循环移除所有的函数对应type事件
          let _events = eventObjs[type];
          if(!_events) return;
          // 倒着循环，移除时数组的顺序不受影响
          for(let i=_events.length-1;i>=0;i--) {
            if(_events[i] === handle) {
              _events.splice(i, 1)
            }
          }
        }
      },
      /*发射事件,触发事件，包装参数，传递给事件处理函数*/
      emit: function(type) {
        // 传参，除了type之外的其他参数，
        // 获取的是arguments从1开始后的所有的参数，是一个数组
        // arguments是一个伪数组，没有slice方法，
        let args = Array.prototype.slice.call(arguments,1)
        // 找到对应的事件
        let _events = eventObjs[type];
        if(!_events) return;
        for(let i=0;i<_events.length;i++) {
          // 如果要绑定上下文就需要使用call或者apply
          _events[i].apply(null, args)
        }
      }
    }
   })()

   /*
   event.on('click',() => {
     console.log('第一个click事件');
   });
   无法移除的函数，注册的时候用的是方法，引用类型，移除的时候肯定也是引用类型，
   两个函数不是同一个函数，
   应该用函数名作为参数，则定义和移除的就都是同一个函数了
   */
  function f() {}
  function foo() {}
   event.on('click',() => {console.log('第一个click事件');}); 
   event.on('click',f); // 可移除
   event.on('click',foo); // 可移除
   console.log(1);
   console.log(1);
   console.log(1);
   function f() {
    event.emit('click')
   }
  </script>
  <script>
    /*
    发布订阅模式
    event.on('click', () => {})
    event.on('click', () => {})
    event.on('click', () => {})


    event.on('meipo', () => {})
    event.on('meipo', () => {})
    event.on('meipo', () => {})

    event.emit('click') click事件会一次被触发
    */
  </script>
</body>
</html>